#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#define PROC_NUM 5
#define BUF_SIZE 4096

#define assert(x, msg)   \
    {                    \
        if (!(x))        \
        {                \
            perror(msg); \
            exit(1);     \
        }                \
    }

// read all into memory buf
static char *read_all(int fd, size_t *sz)
{
    struct stat st;

    // record the current file offset
    assert(fstat(fd, &st) >= 0, "fstat()");
    off_t prev = lseek(fd, 0, SEEK_CUR);
    assert(prev >= 0, "lseek()");

    // move file offset to begin
    assert(lseek(fd, 0, SEEK_SET) >= 0, "lseek()");

    // malloc buf
    char *buf = malloc(st.st_size);
    size_t cur = 0;
    assert(buf != NULL, "malloc");

    while (cur != st.st_size)
    {
        ssize_t r = read(fd, buf + cur, BUF_SIZE);
        assert(r >= 0, "read()");
        if (r == 0)
            break;
        cur += r;
    }

    assert(lseek(fd, prev, SEEK_SET) >= 0, "lseek()");

    *sz = st.st_size;
    return buf;
}

int main(int argc, char **argv)
{
    if (argc < 2)
    {
        fprintf(stderr, "Usage: %s path\n", argv[0]);
        exit(1);
    }

    pid_t pid;

    for (int i = 0; i < PROC_NUM; i++)
    {
        pid = fork();
        assert(pid >= 0, "fork()");

        if (pid != 0)
            continue;

        // child process
        int fd = open(argv[1], O_RDWR | O_EXCL);
        assert(fd >= 0, "open()");

        // lock the file
        lockf(fd, F_LOCK, 0);
        size_t sz = 0;

        // read file into memory
        char *buf = read_all(fd, &sz);
        assert(lseek(fd, 0, SEEK_SET) >= 0, "lseek()");
        int i = atoi(buf);
        i++;

        // open fd as stream
        FILE *f = fdopen(fd, "w");
        fprintf(f, "%d\n", i);
        free(buf);
        fclose(f);
        lockf(fd, F_ULOCK, 0);
        close(fd);
        exit(0);
    }

    for (int i = 0; i < PROC_NUM; i++)
    {
        wait(NULL);
    }

    exit(0);
}